#include "../fs/inode.h"
#include "../fs/fs.h"
#include "../fs/file.h"
#include "../kernel/global.h"
#include "../kernel/memory.h"
#include "../kernel/interrupt.h"
#include "../lib/kernel/list.h"
#include "../lib/kernel/stdio-kernel.h"
#include "../lib/string.h"
#include "../fs/super_block.h"

/* 用来存储inode位置 */
struct inode_position{
    bool two_sec;       // inode是否跨扇区
    uint32_t sec_lba;   // inode 所在的扇区号
    uint32_t off_size;  // inode 在扇区内的字节偏移量
};

/* 获取inode所在的扇区和扇区内偏移量 */
static void inode_locate(struct partition* part, uint32_t inode_no, struct inode_position* inode_pos){
    /* inode_table 在硬盘上是连续的 */
    ASSERT(inode_no < 4096);
    uint32_t inode_table_lba = part->sb->inode_table_lba;

    uint32_t inode_size = sizeof(struct inode);
    uint32_t off_size = inode_no * inode_size;  // 第inode_no 号 I 节点对于 inode_table_lba的字节偏移量 
    uint32_t off_sec = off_size / 512;          // 第inode_no 号 I 节点相对于 inode_table_lba 的扇区偏移量
    uint32_t off_size_in_sec = off_size % 512;     // 待查找的 inode 所在扇区中的起始地址
    

    /* 判断此 i 结点是否跨越2个扇区 */
    uint32_t left_in_sec = 512 - off_size_in_sec;
    if(left_in_sec < inode_size){
        inode_pos->two_sec = true;      //若扇区内剩下的空间不足以容纳一个 inode ，必然是I结点跨越了2个扇区
    }else{
        inode_pos->two_sec = false;
    }
    inode_pos->sec_lba = inode_table_lba + off_sec;     // inode 相对于 磁盘分区 偏移了几个扇区
    inode_pos->off_size = off_size_in_sec;              // inode 相对于自己所在扇区偏移多少个字节 
}

/* 将inode写入到分区part */
void inode_sync(struct partition* part, struct inode* inode, void* io_buf){ // io_buf 用于磁盘io的缓冲区
    uint8_t inode_no = inode->i_no;
    struct inode_position inode_pos;
    inode_locate(part, inode_no, &inode_pos);   //获取 inode 在磁盘分区中的位置信息
    ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));

    /* 硬盘中的 inode 中的成员 inode_tag 和 i_open_cnts 是不需要的，他们只是在内存中记录链表位置和被多少进程共享 */
    struct inode pure_inode;
    memcpy(&pure_inode, inode, sizeof(struct inode));

    /* 以下inode的三个成员只存在于内存中，现在将inode同步到硬盘中，清掉这三项即可 */
    pure_inode.i_open_cnts = 0;
    pure_inode.write_deny = false;      //置为false，以保证在磁盘中读出时可写
    pure_inode.inode_tag.prev = pure_inode.inode_tag.next = NULL;  

    char* inode_buf = (char*)io_buf;
    if(inode_pos.two_sec){          //如果跨了两个扇区，就需要读出两个扇区然后再写入两个扇区
        /* 读写硬盘是以扇区为单位，若写入的数据小于一扇区，要将原磁盘的内容先读出来再和新数据拼成一个扇区后再写入 */
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf,2);

        /* 开始将待写入的 inode 拼入到这2个扇区中的相应位置 */
        memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));    // inode 和 format中写入硬盘时候是连续写入，所以读入2块扇区

        /* 拼接好的数据写入磁盘 */
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 2); 
    }else{
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
        memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    }
}

/* 根据 I 结点号返回相应的 I结点 */
struct inode* inode_open(struct partition* part, uint32_t inode_no){
    /* 先在已打开 inode 链表中找到 inode，此链表是为提速创建的缓冲区 */
    struct list_elem* elem = part->open_inodes.head.next;
    struct inode* inode_found;

    // 根据 inode_no 找到 inode 
    while(elem != &part->open_inodes.tail){
        inode_found = elem2entry(struct inode, inode_tag, elem);
        if(inode_found->i_no == inode_no){
            inode_found->i_open_cnts++;
            return inode_found;
        }
        elem = elem->next;
    }

    /* 由于open_inodes链表中找不到，下面从硬盘上读入此 inode并加入此链表 */
    struct inode_position inode_pos;
    /* inode位置信息会存入 inode_pos,包括inode所在的扇区和扇区内的字节偏移量 */
    inode_locate(part, inode_no, &inode_pos);

    /*为使通过sys_malloc 创建的新 inode被所有任务共享，需要将 inode置于内核空间，故需要临时将 cur_pbc->pgdir置为NULL */
    struct task_struct* cur = running_thread();
    uint32_t* cur_pagedir_bak = cur->pgdir;
    cur->pgdir = NULL;

    /* 以上三行代码完成后下面分配的内存将位于内核区 */
    inode_found = (struct inode*)sys_malloc(sizeof(struct inode));
    /* 恢复 pgdir */
    cur->pgdir = cur_pagedir_bak;

    char* inode_buf;
    if(inode_pos.two_sec){  //考虑跨扇区的情况
        inode_buf = (char*)sys_malloc(1024);    //两个扇区的缓存大小    

        /* I 结点表是被partition_format函数连续写入扇区的，所以下面可以考虑连续读出 */
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
    }else{
        inode_buf = (char*)sys_malloc(512);
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    }

    //获取我们要找的 inode
    memcpy(inode_found, inode_buf + inode_pos.off_size, sizeof(struct inode));  

    /* 因为一会儿很可能会用到此inode，故将其插入到队首便于提前检索到 */
    list_push(&part->open_inodes, &inode_found->inode_tag);
    inode_found->i_open_cnts = 1;

    sys_free(inode_buf);
    return inode_found;
}

/* 关闭 inode 或 减少 inode 的打开数 */
void inode_close(struct inode* inode){
    /* 若没有进程再打开此文件，将此文件inode去掉并释放空间 */
    enum intr_status old_status = intr_disable();
    if(--(inode->i_open_cnts) == 0){
        list_remove(&inode->inode_tag);     //将 I 结点从 part->open_inodes 中去掉
        
        /* inode_open 时为实现inode被所有进程共享
         * 已经在sys_malloc为inode分配内核空间
         * 释放 inode 时也要确保释放的是内核内存 */
        struct task_struct* cur = running_thread();
        uint32_t* cur_pagedir_bak = cur->pgdir;
        cur->pgdir = NULL;
        sys_free(inode);
        cur->pgdir = cur_pagedir_bak;
    }
    intr_set_status(old_status);
}

/* 将硬盘分区 part 上的inode清空 */
void inode_delete(struct partition* part, uint32_t inode_no, void* io_buf){
    ASSERT(inode_no < 4096);
    struct inode_position inode_pos;
    inode_locate(part, inode_no, &inode_pos);   // inode位置信息会存入 inode_pos
    ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));

    char* inode_buf = (char*)io_buf;
    if(inode_pos.two_sec){  //inode跨扇区，读入2个扇区
        /* 将原磁盘是那个的内容先读出来 */
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
        /* 将inode_buf 清0 */
        memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
        /* 用清0的内存数据覆盖磁盘 */
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
    }else{  //未跨扇区，只读入一个扇区就好
        /* 将原硬盘上的内容先读出来 */
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
        /* 将 inode_buf 清0 */
        memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
        /* 用清0的内存数据覆盖磁盘 */
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    }
}

/* 回收inode的数据块和inode本身 */
void inode_release(struct partition* part, uint32_t inode_no){
    struct inode* inode_to_del = inode_open(part, inode_no);
    ASSERT(inode_to_del->i_no == inode_no);

    /* 1.回收inode占用的所有块 */
    uint8_t block_idx = 0, block_cnt = 12;
    uint32_t block_bitmap_idx;
    uint32_t all_blocks[140] = {0};     //12个直接块 + 128个间接块

    /* a 先将前12个块直接存入all_blocks */
    while(block_idx < 12){
        all_blocks[block_idx] = inode_to_del->i_sectors[block_idx];
        block_idx++;
    }

    /* b 如果一级间接块表存在，将其128个间接块读入到 all_block[12~], 并释放一级间接块所占的扇区 */
    if(inode_to_del->i_sectors[12] != 0){
        ide_read(part->my_disk, inode_to_del->i_sectors[12], all_blocks + 12, 1);
        block_cnt = 140;

        /* 回收一级间接块表占用的扇区 */
        block_bitmap_idx = inode_to_del->i_sectors[12] - part->sb->data_start_lba;
        ASSERT(block_bitmap_idx > 0);
        bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
        bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
    }

    /* c inode所有的块地址已经收集到 all_blocks 中，下面逐个回收 */
    block_idx = 0;
    while(block_idx < block_cnt){
        if(all_blocks[block_idx] != 0 ){
            block_bitmap_idx = 0;
            block_bitmap_idx = all_blocks[block_idx] - part->sb->data_start_lba;
            ASSERT(block_bitmap_idx > 0);
            bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
            bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
        }
        block_idx++;    
    }

    /* 2.回收该 inode所占用的inode */
    bitmap_set(&part->inode_bitmap, inode_no, 0);
    bitmap_sync(cur_part, inode_no, INODE_BITMAP);
    
    /********* 以下 inode_delete 是调试使用 ***
     * 此函数会在inode_table 中将要此处inode清 0
     * 但是加上是不需要的， indoe分配是由 inode 位图控制的 ，
     * 硬盘上的数据不需要清 0， 可以直接覆盖
     * ********************/
    void* io_buf = sys_malloc(1024);
    inode_delete(part, inode_no, io_buf);
    sys_free(io_buf);

    /********************************************************/
    inode_close(inode_to_del);

}



/* 初始化 new_inode */ 
void inode_init(uint32_t inode_no, struct inode* new_inode){
    new_inode->i_no = inode_no;
    new_inode->i_size = 0;
    new_inode->i_open_cnts = 0;
    new_inode->write_deny = false;

    /* 初始化块索引数组 */
    uint8_t sec_idx = 0;
    while(sec_idx < 13){
        /* i_sectors[12] 为一级间接块地址 */
        new_inode->i_sectors[sec_idx] = 0 ;
        sec_idx++;
    }
}